/**
 * Copyright 2016-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.inmorn.redisclient;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import com.inmorn.extspring.util.StringUtils;

import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.util.Hashing;
import redis.clients.util.Sharded;

/**
 * 环绕切面注解:com.inmorn.redisclient.Redis
 * 提供应用操作redis,获取pool连接及还连接
 * @author Jeff.Li
 * 2017-10-27
 */
@Aspect
@Component
public class RedisAspect implements InitializingBean,
				ApplicationContextAware,EnvironmentAware{
	private Log logger = LogFactory.getLog(getClass());
	
	/**
	 * application.properties文件中配置
	 */
	private String shardeds;
	
	/**
	 * 存放已经初始化的shardedsJedisPool
	 */
	protected Map<String, ShardedJedisPool> shardedJedisPoolMap = 
			new HashMap<String, ShardedJedisPool>();
	/**
	 * 存当前线程的shardedJedis
	 */
	private static ThreadLocal<ShardedJedis> shardedJedises = 
			new ThreadLocal<ShardedJedis>(){};
	/**
	 * 存当前线程的shardedJedis other
	 */
	private static ThreadLocal<ShardedJedis> shardedJedisesOther = 
			new ThreadLocal<ShardedJedis>(){};
			
	private Properties properties = new Properties();
	private Environment environment;
	
	public static ShardedJedis getCurShardedJedis() {
		return shardedJedises.get();
	}
	
	public static ShardedJedis getCurShardedJedisOther() {
		return shardedJedisesOther.get();
	}
	
	protected int JEDIS_TIMEOUT = 100;
	protected int DEFAULT_POOL_CONFIG_maxActive = 50;
	protected int DEFAULT_POOL_CONFIG_maxIdle = 5;
	protected int DEFAULT_POOL_CONFIG_maxWait = 100;
	protected boolean DEFAULT_POOL_CONFIG_testOnBorrow = false;
	
	@Around(value = "@annotation(com.inmorn.redisclient.Redis)")
	public Object preAndRelResource(ProceedingJoinPoint joinpoint) {
		Signature signature = joinpoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        Redis redis = method.getAnnotation(Redis.class);
        String sharded = redis.sharded();
        String shardedOther = redis.shardedOther();
        
        ShardedJedis shardedJedis = null;
        ShardedJedis shardedJedisOther = null;
        ShardedJedisPool pool = null;
        ShardedJedisPool poolOther = null;
		try {
			pool = shardedJedisPoolMap.get(sharded);
			shardedJedis = pool.getResource();
	        if(shardedJedis == null){
	        	logger.error("Get redis shardedJedis resource is null");
				return null;
	        }
	        shardedJedises.set(shardedJedis);
	        
	        if(StringUtils.isNotEmpty(shardedOther)){
	        	poolOther = shardedJedisPoolMap.get(shardedOther);
	        	shardedJedisOther = poolOther.getResource();
	        	if(shardedJedisOther == null){
		        	logger.error("Get redis shardedJedisOther resource is null");
					return null;
		        }
	        	shardedJedisesOther.set(shardedJedisOther);
	        }
	        
			Object returnValue = null;
			returnValue = joinpoint.proceed();
			if (null != shardedJedis)
				pool.returnResource(shardedJedis);
			if (null != shardedJedisOther)
				poolOther.returnResource(shardedJedisOther);
			return returnValue;
		} catch (Throwable e) {
			logger.error("Access cache error:");
			logger.error(e.getMessage(), e);
			if (e instanceof JedisException) {
				if (null != shardedJedis)
					pool.returnBrokenResource(shardedJedis);
				if (null != shardedJedisOther)
					poolOther.returnBrokenResource(shardedJedisOther);
			} else {
				if (null != shardedJedis)
					pool.returnResource(shardedJedis);
				if (null != shardedJedisOther)
					poolOther.returnResource(shardedJedisOther);
			}
			return null;
		} finally {
			shardedJedises.remove();
			shardedJedisesOther.remove();
			logger.debug("after aop process");
		}
	}
	
	public ShardedJedisPool createShardedJedisPool(String sharded){
		ShardedJedisPool shardedJedisPool = null;
		String hostPortsStr = getProperty(sharded + ".hostPorts");
		if(StringUtils.isEmpty(hostPortsStr)){
			throw new RuntimeException("sharded:" + sharded + ".hostPorts in application.properties is null");
		}
		int timeOut = JEDIS_TIMEOUT;
		try{ timeOut = Integer.parseInt(getProperty(sharded + ".timeOut")); }catch(Exception e){ }
		int maxActive = DEFAULT_POOL_CONFIG_maxActive;
		try{ maxActive = Integer.parseInt(getProperty(sharded + ".poolConfig.maxActive")); }catch(Exception e){}
		int maxIdle = DEFAULT_POOL_CONFIG_maxIdle;
		try{ maxIdle = Integer.parseInt(getProperty(sharded + ".poolConfig.maxIdle")); }catch(Exception e){ }
		int maxWait = DEFAULT_POOL_CONFIG_maxWait;
		try{ maxWait = Integer.parseInt(getProperty(sharded + ".poolConfig.maxWait")); }catch(Exception e){ }
		boolean testOnBorrow = DEFAULT_POOL_CONFIG_testOnBorrow;
		try{ testOnBorrow = Boolean.parseBoolean(getProperty(sharded + ".poolConfig.testOnBorrow")); }catch(Exception e){ }
		
		logger.debug("Prepare to do initJedisPool");
		List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>();
		String[] hostPorts = hostPortsStr.split(",");
		for (String hostPort : hostPorts) {
			String[] redis = hostPort.split(":");
			String host = redis[0];
			Integer port = Integer.parseInt(redis[1]);
			if(StringUtils.isNotEmpty(host) && port != null && port.intValue() > 0){
				if(timeOut > 0) {
					JedisShardInfo si = new JedisShardInfo(host, port.intValue(), timeOut);
					shards.add(si);
				} else {
					JedisShardInfo si = new JedisShardInfo(host, port.intValue());
					shards.add(si);
				}
			}
		}
		// pool = new ShardedJedisPool(new JedisPoolConfig(), shards,
		// Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN);
		
		JedisPoolConfig jConfg = new JedisPoolConfig();
		if(maxActive > 0) jConfg.setMaxActive(maxActive);
		if(maxIdle > 0) jConfg.setMaxIdle(maxIdle);
		if(maxWait > 0) jConfg.setMaxWait(maxWait);
		if(testOnBorrow) jConfg.setTestOnBorrow(testOnBorrow);
		
		shardedJedisPool = new ShardedJedisPool(jConfg, shards,
				Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN);
		shardedJedisPoolMap.put(sharded,shardedJedisPool);
		/**
		 * pool默认配置有问题
		 */
		logger.debug("finished initJedisPool");
		return shardedJedisPool;
	}
	
	protected String getProperty(String key){
		String value = properties.getProperty(key);
		if(StringUtils.isEmpty(value)){
			return environment.getProperty(key);
		}
		return value;
	}

	public void afterPropertiesSet() throws Exception {
		shardeds = getProperty("shardeds");
		if(StringUtils.isNotEmpty(shardeds)){
			for(String sharded : shardeds.split(",")){
				logger.info("sharded:" + sharded + " begin create...");
				createShardedJedisPool(sharded);
				logger.info("sharded:" + sharded + " create success");
			}
		}
	}

	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		try{
			properties = applicationContext.getBean("configProperties",Properties.class);
		}catch(Exception e){
			
		}
	}

	public void setEnvironment(Environment environment) {
		this.environment = environment;
	}

}
